package com.aerors.sbgt.service;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.GridReaderLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.ChannelSelection;
import org.geotools.styling.ContrastEnhancement;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SLD;
import org.geotools.styling.SelectedChannelType;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.json.JSONArray;
import org.json.JSONObject;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory2;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.style.ContrastMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.aerors.sbgt.dao.RasterLayerInfoRepository;
import com.aerors.sbgt.entity.Msg;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

@Service
public class WMSService {
	private StyleFactory sf = CommonFactoryFinder.getStyleFactory();
	private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
	private GridCoverage2DReader reader;
	@Autowired
	private RasterLayerInfoRepository rasterLayerInfoRepository;
	
	public byte[] getMap(String bbox,String width,String height,String layer) { 
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		if(rasterLayerInfoRepository.existsByName(layer)) {
			String[] bboxStrArray = bbox.split(",");
			double[] coors = new double[4];
			for(int i=0;i<bboxStrArray.length;i++) {
				coors[i] = Double.parseDouble(bboxStrArray[i]);
			}
			String layerURL = rasterLayerInfoRepository.findByName(layer).get(0).getUrl();
			
			MapContent map = new MapContent();
			File rasterFile = new File(layerURL);

			AbstractGridFormat format = GridFormatFinder.findFormat(rasterFile);
			Hints hints = new Hints();
			if (format instanceof GeoTiffFormat) {
				hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
			}
			reader = format.getReader(rasterFile, hints);
			Style rasterStyle = createRGBStyle();
			Layer rasterLayer = new GridReaderLayer(reader, rasterStyle);
			map.addLayer(rasterLayer);
			
			try {
				double x1 = coors[0], y1 = coors[1], x2 = coors[2], y2 = coors[3];

				CoordinateReferenceSystem clienCRS = map.getCoordinateReferenceSystem();
				CoordinateReferenceSystem serverCRS = CRS.decode("EPSG:3857");
				
				if (!clienCRS.equals(serverCRS)) {
					boolean lenient = true; // allow for some error due to different datums
					MathTransform transform = CRS.findMathTransform(serverCRS, clienCRS, lenient);
					GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();

					Coordinate coord1 = new Coordinate(x1, y2);
					Coordinate coord2 = new Coordinate(x2, y1);
					Geometry clientPoint1 = geometryFactory.createPoint(coord1);
					Geometry clientPoint2 = geometryFactory.createPoint(coord2);

					Geometry serverPoint1 = JTS.transform(clientPoint1, transform);
					Geometry serverPoint2 = JTS.transform(clientPoint2, transform);
					x1 = serverPoint1.getCoordinate().x;
					y2 = serverPoint1.getCoordinate().y;
					
					x2 = serverPoint2.getCoordinate().x;
					y1 = serverPoint2.getCoordinate().y;
				}
				ReferencedEnvelope mapArea = new ReferencedEnvelope(x1, x2, y1, y2, clienCRS);
				// 初始化渲染器
				StreamingRenderer sr = new StreamingRenderer();
				sr.setMapContent(map);
				// 初始化输出图像
				BufferedImage bi = new BufferedImage(Integer.parseInt(width), Integer.parseInt(height), BufferedImage.TYPE_INT_ARGB);
				Graphics g = bi.getGraphics();
				((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
						RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
				Rectangle rect = new Rectangle(0, 0, Integer.parseInt(width), Integer.parseInt(height));
				// 绘制地图
				// sr.paint((Graphics2D) g, rect, map.getMaxBounds());
				sr.paint((Graphics2D) g, rect, mapArea);
//				ImageIO.write(bi, "png", new File("D:\\workData\\111.png"));
				ImageIO.write(bi, "png", baos);
				map.dispose();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if(baos == null) {
			return "".getBytes();
		}else {
			return baos.toByteArray();
		}
	}
	
	public MapContent findRasterFile(String rasterFileName) {
		MapContent map = new MapContent();
		File rasterFile = new File(rasterFileName);

		AbstractGridFormat format = GridFormatFinder.findFormat(rasterFile);
		Hints hints = new Hints();
		if (format instanceof GeoTiffFormat) {
			hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
		}
		reader = format.getReader(rasterFile, hints);
		Style rasterStyle = createRGBStyle();
		Layer rasterLayer = new GridReaderLayer(reader, rasterStyle);
		map.addLayer(rasterLayer);
		return map;
	}
	
	public byte[] drawPNG(MapContent map,double[] bbox,int width,int height) {
		ByteArrayOutputStream baos = null;
		try {
			double x1 = bbox[0], y1 = bbox[1], x2 = bbox[2], y2 = bbox[3];

			CoordinateReferenceSystem clienCRS = map.getCoordinateReferenceSystem();
			CoordinateReferenceSystem serverCRS = CRS.decode("EPSG:3857");
			
			if (!clienCRS.equals(serverCRS)) {
				boolean lenient = true; // allow for some error due to different datums
				MathTransform transform = CRS.findMathTransform(serverCRS, clienCRS, lenient);
				GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();

				Coordinate coord1 = new Coordinate(x1, y2);
				Coordinate coord2 = new Coordinate(x2, y1);
				Geometry clientPoint1 = geometryFactory.createPoint(coord1);
				Geometry clientPoint2 = geometryFactory.createPoint(coord2);

				Geometry serverPoint1 = JTS.transform(clientPoint1, transform);
				Geometry serverPoint2 = JTS.transform(clientPoint2, transform);
				x1 = serverPoint1.getCoordinate().x;
				y2 = serverPoint1.getCoordinate().y;
				
				x2 = serverPoint2.getCoordinate().x;
				y1 = serverPoint2.getCoordinate().y;
			}
			ReferencedEnvelope mapArea = new ReferencedEnvelope(x1, x2, y1, y2, clienCRS);
			// 初始化渲染器
			StreamingRenderer sr = new StreamingRenderer();
			sr.setMapContent(map);
			// 初始化输出图像
			BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
			Graphics g = bi.getGraphics();
			((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
					RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			Rectangle rect = new Rectangle(0, 0, width, height);
			// 绘制地图
			// sr.paint((Graphics2D) g, rect, map.getMaxBounds());
			sr.paint((Graphics2D) g, rect, mapArea);
			ImageIO.write(bi, "png", baos);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return baos.toByteArray();
	}
	
	/**
	 * This method examines the names of the sample dimensions in the provided
	 * coverage looking for "red...", "green..." and "blue..." (case insensitive
	 * match). If these names are not found it uses bands 1, 2, and 3 for the red,
	 * green and blue channels. It then sets up a raster symbolizer and returns this
	 * wrapped in a Style.
	 *
	 * @return a new Style object containing a raster symbolizer set up for RGB
	 *         image
	 */
	private Style createRGBStyle() {
		GridCoverage2D cov = null;
		try {
			cov = reader.read(null);
		} catch (IOException giveUp) {
			throw new RuntimeException(giveUp);
		}
		// We need at least three bands to create an RGB style
		int numBands = cov.getNumSampleDimensions();
		if (numBands < 3) {
			return null;
		}
		// Get the names of the bands
		String[] sampleDimensionNames = new String[numBands];
		for (int i = 0; i < numBands; i++) {
			GridSampleDimension dim = cov.getSampleDimension(i);
			sampleDimensionNames[i] = dim.getDescription().toString();
		}
		final int RED = 0, GREEN = 1, BLUE = 2;
		int[] channelNum = { -1, -1, -1 };
		// We examine the band names looking for "red...", "green...", "blue...".
		// Note that the channel numbers we record are indexed from 1, not 0.
		for (int i = 0; i < numBands; i++) {
			String name = sampleDimensionNames[i].toLowerCase();
			if (name != null) {
				if (name.matches("red.*")) {
					channelNum[RED] = i + 1;
				} else if (name.matches("green.*")) {
					channelNum[GREEN] = i + 1;
				} else if (name.matches("blue.*")) {
					channelNum[BLUE] = i + 1;
				}
			}
		}
		// If we didn't find named bands "red...", "green...", "blue..."
		// we fall back to using the first three bands in order
		if (channelNum[RED] < 0 || channelNum[GREEN] < 0 || channelNum[BLUE] < 0) {
			channelNum[RED] = 1;
			channelNum[GREEN] = 2;
			channelNum[BLUE] = 3;
		}
		// Now we create a RasterSymbolizer using the selected channels
		SelectedChannelType[] sct = new SelectedChannelType[cov.getNumSampleDimensions()];
		ContrastEnhancement ce = sf.contrastEnhancement(ff.literal(1.0), ContrastMethod.NORMALIZE);
		for (int i = 0; i < 3; i++) {
			sct[i] = sf.createSelectedChannelType(String.valueOf(channelNum[i]), ce);
		}
		RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
		ChannelSelection sel = sf.channelSelection(sct[RED], sct[GREEN], sct[BLUE]);
		sym.setChannelSelection(sel);

		return SLD.wrapSymbolizers(sym);
	}

	/**
	 * Create a Style to display the specified band of the GeoTIFF image as a
	 * greyscale layer.
	 * <p>
	 * This method is a helper for createGreyScale() and is also called directly by
	 * the displayLayers() method when the application first starts.
	 *
	 * @param band
	 *            the image band to use for the greyscale display
	 *
	 * @return a new Style instance to render the image in greyscale
	 */
	private Style createGreyscaleStyle(int band) {
		ContrastEnhancement ce = sf.contrastEnhancement(ff.literal(1.0), ContrastMethod.NORMALIZE);
		SelectedChannelType sct = sf.createSelectedChannelType(String.valueOf(band), ce);

		RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
		ChannelSelection sel = sf.channelSelection(sct);
		sym.setChannelSelection(sel);

		return SLD.wrapSymbolizers(sym);
	}
}
